import java.io.*;
import java.math.BigInteger;
import java.net.*;

import javax.crypto.SecretKey;

public class Server{

	// Declaration section:
	// declare a server socket and a client socket for the server
	// declare an input and an output stream

	static  Socket clientSocket = null;
	static  ServerSocket serverSocket = null;

	// This chat server can accept up to 10 clients' connections

	static  clientThread t[] = new clientThread[10];      
	static Encryption e[] = new Encryption[10];

	public static void main(String args[]) {

		// The default port

		int port_number=2323;

		if (args.length < 1)
		{
			System.out.println("Usage: java MultiThreadChatServer \n"+
					"Now using port number="+port_number);
		} else {
			port_number=Integer.valueOf(args[0]).intValue();
		}

		// Initialization section:
		// Try to open a server socket on port port_number (default 2222)
		// Note that we can't choose a port less than 1023 if we are not
		// privileged users (root)

		try {
			serverSocket = new ServerSocket(port_number);
		}
		catch (IOException e)
		{System.out.println(e);}

		// Create a socket object from the ServerSocket to listen and accept 
		// connections.
		// Open input and output streams for this socket will be created in 
		// client's thread since every client is served by the server in
		// an individual thread

		while(true){
			try {
				clientSocket = serverSocket.accept();
				for(int i=0; i<=9; i++){
					if(t[i]==null)
					{
						(t[i] = new clientThread(clientSocket,t,e)).start();
						break;
					}
				}
			}
			catch (IOException e) {
				System.out.println(e);}
		}
	}
} 

// This client thread opens the input and the output streams for a particular client,
// ask the client's name, informs all the clients currently connected to the 
// server about the fact that a new client has joined the chat room, 
// and as long as it receive data, echos that data back to all other clients.
// When the client leaves the chat room this thread informs also all the
// clients about that and terminates. 

class clientThread extends Thread{

	BufferedReader in = null;
	PrintWriter out = null;
	DataOutputStream dOS = null;
	DataInputStream dIS = null;
	Socket clientSocket = null;       
	clientThread t[]; 
	DiffieHellman dh;
	int privateKey;
	byte[] publicKey;
	byte[] serverKey;
	SecretKey mainKey;
	Encryption e[];
	int id = -1;




	public clientThread(Socket clientSocket, clientThread[] t, Encryption[] e){
		this.clientSocket=clientSocket;
		this.t=t;
		this.e=e;
	}

	@SuppressWarnings("deprecation")
	public void run() 
	{
		String line;
		String name;
		String password;
		byte[] apub = null;
		String apubstr = "";
		byte[] bpub;
		String bpubstr;
		
		//Set the Thread's ID
		for(int i=0; i<=9; i++)
			if (t[i]==this)
				id = i;  
		
		try{
			dh = new DiffieHellman();
			//BigInteger[] pg = dh.GeneratePG();
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			out = new PrintWriter(clientSocket.getOutputStream(), true);
			dOS = new DataOutputStream(clientSocket.getOutputStream());
			dIS = new DataInputStream(clientSocket.getInputStream());
			BigInteger[] pg = new BigInteger[2];

			int bytsToRead = 0;
			synchronized (clientSocket) {
				pg[0] = new BigInteger(in.readLine());
			}
			synchronized (clientSocket) {
				pg[1] = new BigInteger(in.readLine());
			}
			synchronized (clientSocket) {
				bytsToRead = Integer.parseInt(in.readLine());
			}
			
			apub = new byte[bytsToRead];
			System.out.println("Byte Length: " + apub.length);
			
			synchronized (clientSocket) {
				//dIS.readFully(apub);
				System.out.println("Getting ready to read dIS");
				dIS.read(apub, 0, apub.length);
				System.out.println("reading dIS");
			}
//			System.out.println(apub.toString());
//			 for(byte b : apub) {
// 				System.out.print(b + ", ");
// 			}
			
			
			
			bpub = dh.CreateSendInteger(pg[0], pg[1], 16);

			System.out.println("Apub: "+ apub + "\nBpub: " + bpub);
			//Thread.yield();
			synchronized (clientSocket) {
				//dOS.write(bpub, 0, bpub.length);
				//dOS.writeInt(bpub.length);
				
				
				 //dOS.writeBytes(bpub.length + "\n");
				 dOS.writeInt(bpub.length);
			}
			
			synchronized (clientSocket) {
				dOS.write(bpub);
			}

            
			SecretKey Kb = dh.CreateFullKey(apub);
			
			e[id] = new Encryption(Kb);
			String encrypted = "";
			
		synchronized (clientSocket) {

			encrypted = e[id].encrypt(encrypted);
		}
		System.out.println("Encrypted String: " + encrypted  + " Length:  "+ encrypted.length());
			String decrypted = e[id].decrypt(encrypted);
			System.out.println("decrypted");
			System.out.println(decrypted);
		
			
			//Diffie Hellman ends here
			
			out.println(e[id].encrypt("Enter your name."));
			name = e[id].decrypt(in.readLine());
			out.println(e[id].encrypt("Enter your password."));
			password = e[id].decrypt(in.readLine());
			
			boolean auth = Authentication.verify(name, password);
			try {
				Thread.sleep(1000l);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if(auth) {
				String welcome = e[id].encrypt("Hello "+name+" to our chat room. To leave enter /quit in a new line");
				if(welcome.contains("\n")) System.out.println("WELCOME HAS NEW LINE IN MIDDLE!");
				EncComm.send(out, e[id], "Hello "+name+" to our chat room. To leave enter /quit in a new line");
				//out.println(welcome);
				
				//Make sure that each active thread has an encryption.
				for(int i=0; i<=9; i++){
					if(t[i]!=null && e[i] == null){
						t[i] = null;
						System.out.println("The following thread was terminated(casually): "+ i);
					}
				}
				for(int i=0; i<=9; i++)
					if (t[i]!=null && t[i]!=this){
						if(e[i] == null)
							System.out.println("TRYING TO ACCESS NULL ENCRYPTION!");
						t[i].out.println(e[i].encrypt("*** A new user "+name+" entered the chat room !!! ***" ));
					}//1234567890123456789012345678901234567891111111
				while (true) {
					line = e[id].decrypt(in.readLine());
					if(line.startsWith("/quit")) break; 
					for(int i=0; i<=9; i++){
						if (t[i]!=null){
							String temp = e[i].encrypt("<"+name+"> "+line);
							t[i].out.println(temp);
							System.out.println("<"+name+"> Encrypted: "+temp);
							//t[i].out.println(e[i].encrypt("<"+name+"> "+line));
						}
					}
				}
				for(int i=0; i<=9; i++)
					if (t[i]!=null && t[i]!=this)  
						t[i].out.println(e[i].encrypt("*** The user "+name+" is leaving the chat room !!! ***" ));
	
				out.println(e[id].encrypt("*** Bye "+name+" ***")); 
	
				// Clean up:
				// Set to null the current thread variable such that other client could
				// be accepted by the server
	
				t[id]=null;
				e[id]=null;
	
				// close the output stream
				// close the input stream
				// close the socket
	
				in.close();
				out.close();
				clientSocket.close();
			}else{
				out.println(e[id].encrypt("Username and/or Password not recognized."));
			}
		}
		catch(IOException e){};
	}
}